package ms.unicorn.simulation;

import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

public class FDGraph {
	
	private final FDNode root;
	private final List<FDNode> nodes = new ArrayList<FDNode>();
	private final int node_count[] = new int[] {0, 0};
	long timeInitialization, timePropagation;
	Map<FDNode, Integer> reach;
	
	public FDGraph(int n, int height) {
		root = createNode(FDNode.FDNODE_REQUIREMENT, "root");
		List<FDNode> layer = new ArrayList<FDNode>(n);
		List<FDNode> prevlayer = new ArrayList<FDNode>(n);
		// create leaf nodes
		for (int i=1; i<=n; prevlayer.add(createNode(FDNode.FDNODE_REQUIREMENT, String.format("L%d", i))), i++);
		
		// create internal nodes
		for(int h = height-1; h > 1; h--) {
			int type = FDNode.FDNODE_REQUIREMENT;
			layer.clear();
			for (int i = 1; i <= n; i++ ) {
				FDNode node = createNode(type, String.format("n%d-%d", h, i));
				for(int j = 0; j < n; j++) {
					FDNode o = createNode(FDNode.FDNODE_COMPOUND, "");
					prevlayer.get(j).addParentNode(o);
					//o.getChildNode().add(prevlayer.get(j));
					o.addParentNode(node);
				}
				layer.add(node);
			}
			List<FDNode> swap = prevlayer;
			prevlayer = layer;
			layer = swap;
		}
		// connect to root
		for(int j = 0; j < n; j++) {
			FDNode o = createNode(FDNode.FDNODE_COMPOUND, "");
			//o.getChildNode().add(prevlayer.get(j));
			prevlayer.get(j).addParentNode(o);
			o.addParentNode(root);
		}
	}

	@Override
	public String toString() {
		return root.toString();
	}
	
	protected FDNode createNode(int nodetype, String name) {
		FDNode node = new FDNode(nodetype, name);
		nodes.add(node);
		node_count[nodetype]++;
		return node;
	}
	
	public DAT propagateDAT() {
		DAT rootDAT = null;
		Queue<FDNode> Q = new LinkedBlockingQueue<FDNode>();
		long timeBegin = System.nanoTime();
		initializeDAT(Q);
		timeInitialization = System.nanoTime() - timeBegin;
		
		while (!Q.isEmpty()) {
			FDNode x = Q.poll();
			if (reach.get(x) == 0) { // check whether the node is ready, but not visited
				//System.out.printf("Visit node %s\n", x.getName());
				reach.put(x, -1);
				for(FDNode child: x.getChildNode())  // for each incoming node
					switch (x.getNodetype()) {
					case FDNode.FDNODE_REQUIREMENT:
						x.setDAT(DAT.concat(x.getDAT(), child.getDAT()));
						break;
					case FDNode.FDNODE_COMPOUND:
						x.setDAT(DAT.join(x.getDAT(), child.getDAT()));
						break;
					}
				for (FDNode parent: x.getParentNode()) {
					int r = reach.get(parent) - 1;
					reach.put(parent, r);
					if (r==0)
						Q.add(parent);
				}
			}
		}
		timePropagation = System.nanoTime() - timeBegin - timePropagation;
		return rootDAT;
	}
	
	public void initializeDAT(Queue<FDNode> Q) {
		Q.clear();
		reach = new HashMap<FDNode, Integer>();
		for(FDNode node: getNodes()) 
			if (node.isLeaf()) {
				DAT dat = new DAT();
				dat.addAlternative(node, 1, 0);
				node.setDAT(dat);
				reach.put(node, 0);
				Q.add(node);
			}
			else {
				node.setDAT(null);
				reach.put(node, node.getChildNode().size());
			}
	}
	
	public FDNode getRoot() {
		return root;
	}

	public int[] getNodesCount() {
		return node_count;
	}

	public List<FDNode> getNodes() {
		return nodes;
	}
}
